/*
 * Funny Language - a free style programming language.
 * Copyright (C) 2015 by fanguangping (fanguangping@163.com)
 * lexer.c
 */

#include <stdlib.h>
#include <stdio.h>
#include "lexer.h"
#include "../config/global.h"
#include "../base/cmemory.h"

#define START_TOKEN       "<1>"
#define DEFAULT_ACTION    "($)"
#define OPEN_CHAR         '<'
#define CLOSE_CHAR        '>'
#define ESCAPE_CHAR       '\\'

typedef struct _PatternNode {
    String regex;
    String target;
    Map map;
} PatternNode;

static Map pattern_cache = NULL;
static int inited = FALSE;

static PatternNode *alloc_pattern_node() {
    return (PatternNode *) sys_malloc(sizeof(PatternNode));
}

static String string_pattern(String format, String string) {
    String str = alloc_string(512);
    snprintf(str, 512 * sizeof(char), format, string);
    return str;
}
static String number_pattern_1(String format, int n) {
    String str = alloc_string(10);
    snprintf(str, 10 * sizeof(char), format, n);
    return str;
}
static String number_pattern_2(String format, String key, int n) {
    String str = alloc_string(10);
    snprintf(str, 10 * sizeof(char), format, key, n);
    return str;
}

String replace_string_with_number(String orig, char open, char close, char escape) {
    String string = alloc_string(512);
    String result = string;
    char *p = orig;
    int id = 1;
    int opened = FALSE;

    while(*p) {
        if (*p == escape) {
            if (*(p + 1)) {
                if (opened == FALSE) {
                    if (*(p + 1) != open && *(p + 1) != close) {
                        *string++ = *p;
                    }
                    *string++ = *(p + 1);
                }
                p = p + 2;
                continue;
            } else {
                *string = 0;
                break;
            }
        }
        if (*p == open) {
            opened = TRUE;
            *string++ = *p;
            *string++ = *(number_pattern_1("%d", id));
            id++;
        }
        if (*p == close) {
            opened = FALSE;
        }
        if (opened == FALSE) {
            *string++ = *p;
        }
        p++;
    }
    *string = 0;

    return result;
}

Rule *alloc_rule() {
    return (Rule *) sys_malloc(sizeof(Rule));
}

Pattern *init_pattern(String regex, String target) {
    Pattern *p = (Pattern *) sys_malloc(sizeof(Pattern));
    p->regex = regex;
    p->target = target;
    return p;
}

static Rule *find_rule(String token) {
    Rule *rule;
    hashmap_get(globalFunny->rules, token, (void**)(&rule));
    return rule;
}

static void merge_rule(PatternNode *p, String tokenId, Pattern *pattern) {
    if (equals_string(pattern->target, DEFAULT_ACTION)) {
        p->regex = string_replace(copy_string(p->regex), string_pattern("<%s>", tokenId), string_pattern("<%s1>", tokenId));
        p->target = string_replace(copy_string(p->target), string_pattern("$%s", tokenId), string_pattern("$%s1", tokenId));
    } else {
        String r = replace_string_with_number(copy_string(pattern->regex), OPEN_CHAR, CLOSE_CHAR, ESCAPE_CHAR);
        String t = pattern->target;
        int i = 1;
        String* tokens = fetch_string_from_paren_with_escape(pattern->regex, OPEN_CHAR, CLOSE_CHAR, ESCAPE_CHAR);
        while (*tokens) {
            r = string_replace(copy_string(r), number_pattern_1("<%d>", i), number_pattern_2("<%s%d>", tokenId, i));
            t = string_replace(copy_string(t), number_pattern_1("$%d", i), number_pattern_2("$%s%d", tokenId, i));
            i++;
            tokens++;
        }

        if (equals_string(p->target, DEFAULT_ACTION)) {
            p->regex = r;
            p->target = t;
        } else {
            p->regex = string_replace(copy_string(p->regex), string_pattern("<%s>", tokenId), r);
            p->target = string_replace(copy_string(p->target), string_pattern("$%s", tokenId), t);
        }
    }
}

static void put_rules_in_map(PatternNode *p, String tokenId, Pattern *pattern) {
    String* tokens = fetch_string_from_paren_with_escape(pattern->regex, OPEN_CHAR, CLOSE_CHAR, ESCAPE_CHAR);
    int j = 1;
    String token;
    Rule *node;
    while (*tokens) {
        token = *tokens;
        String newTokenId = number_pattern_2("%s%d", tokenId, j);
        node = find_rule(token);
        hashmap_put(p->map, newTokenId, node);
        j++;
        tokens++;
    }
}

static void merge_group(PatternNode *p, String tokenId, String pTarget, String target) {
    if (equals_string(pTarget, DEFAULT_ACTION)) {
        p->regex = string_pattern("<%s>", tokenId);
        p->target = copy_string(target);
    } else {
        p->target = string_replace(copy_string(p->target), string_pattern("$%s", tokenId), target);
    }
}

static void put_group_token_in_map(PatternNode *p, String tokenId, Pattern *pattern) {
    String* tokens = fetch_string_from_paren_with_escape(pattern->regex, OPEN_CHAR, CLOSE_CHAR, ESCAPE_CHAR);
    String patRegex = pattern->regex;
    String token;
    Rule *node;
    while (*tokens) {
        token = *tokens;
        node = find_rule(token);
        patRegex = string_replace(copy_string(patRegex), string_pattern("<%s>", token), node->regex);
        tokens++;
    }
    Rule *r = alloc_rule();
    r->type = TOKEN;
    r->regex = patRegex;
    hashmap_put(p->map, tokenId, r);
}

static Queue *recur_pattern(PatternNode *pattern) {
    Queue *queue = init_queue();
    Map map = pattern->map;
    int i = 0;
    Rule *rule;
    String *keys = hashmap_keys(map);
    int allTokens = TRUE;

    for (i = 0; i< hashmap_length(map); i++) {
        if (keys[i]) {
            hashmap_get(map, keys[i], (void**)(&rule));
            if (rule->type != TOKEN) {
                allTokens = FALSE;
                Queue *q = rule->patterns;
                QueueNode *qnode = q->head;
                while (qnode != NULL) {
                    String tokenId = copy_string(keys[i]);
                    Pattern *pat = (Pattern *)qnode->element;

                    PatternNode *p = alloc_pattern_node();
                    p->regex = copy_string(pattern->regex);
                    p->target = copy_string(pattern->target);
                    p->map = hashmap_copy(pattern->map);

                    if (rule->type == RULE) {
                        merge_rule(p, tokenId, pat);
                        hashmap_remove(p->map, tokenId);
                        put_rules_in_map(p, tokenId, pat);
                    } else {
                        String patTarget = string_replace(copy_string(pat->target), "$", string_pattern("$%s", tokenId));
                        merge_group(p, tokenId, pattern->target, patTarget);
                        put_group_token_in_map(p, tokenId, pat);
                    }
                    append_queue(queue, recur_pattern(p));
                    qnode = qnode->next;
                }
            }
        }
    }
    if (allTokens) {
        enqueue(queue, pattern);
    }
    return queue;
}

static String pattern_to_function(PatternNode *pattern, String text) {
    String regex = copy_string(pattern->regex);
    String target = copy_string(pattern->target);
    Map map = pattern->map;
    Rule *rule;
    String *keys = hashmap_keys(map);
    int groups = 0;
    int i;
    for (i = 0; i< hashmap_length(map); i++) {
        if (keys[i]) {
            hashmap_get(map, keys[i], (void**)(&rule));
            regex = string_replace(copy_string(regex), string_pattern("<%s>", keys[i]), rule->regex);
            groups++;
        }
    }
    String* regex_groups = match_regex_groups(regex, groups + 1, text);
    if (regex_groups == NULL) {
        return NULL;
    }
    String* tokens = fetch_string_from_paren_with_escape(pattern->regex, OPEN_CHAR, CLOSE_CHAR, ESCAPE_CHAR);
    if (!equals_string(text, *regex_groups)) {
        return NULL;
    }
    if (groups == 1 && *(regex_groups + 1) == NULL) {
        target = string_replace(target, string_pattern("$%s", *tokens), *regex_groups);
    } else {
        while (*(regex_groups + 1)) {
            String group = *(regex_groups + 1);
            String token = *tokens;
            target = string_replace(target, string_pattern("$%s", token), group);
            regex_groups++;
            tokens++;
        }
    }
    return target;
}

static PatternNode *init_pattern_node(String token) {
    Rule *node = find_rule(token);
    PatternNode *p = alloc_pattern_node();
    p->regex = START_TOKEN;
    p->target = DEFAULT_ACTION;
    p->map = hashmap_new();
    hashmap_put(p->map, "1", node);
    return p;
}

String lex_to_function(String token, String text) {
    Queue *queue;
    hashmap_get(pattern_cache, token, (void**)(&queue));
    if (queue == NULL) {
        return NULL;
    }
    QueueNode *node = queue->head;
    int i = 0;
    while (node != NULL) {
        PatternNode *patternNode = (PatternNode *)node->element;
        String string = pattern_to_function(patternNode, text);
        if (string != NULL) {
            return string;
        }
        node = node->next;
        i++;
    }
    return NULL;
}

String expand_lex(String text) {
    String *orders = globalFunny->lexOrders;
    while (*orders) {
        String string = lex_to_function(*orders, text);
        if (string != NULL) {
            return string;
        }
        orders++;
    }
    return NULL;
}

void init_lexer() {
    if (!inited) {
        globalFunny->rules = hashmap_new();
        inited = TRUE;
    }
}

void register_rule(String token, Queue *patterns) {
    init_lexer();
    Rule *rule = alloc_rule();
    rule->type = RULE;
    rule->patterns = patterns;
    hashmap_put(globalFunny->rules, token, rule);
}

void register_group(String token, Queue *patterns) {
    init_lexer();
    Rule *rule = alloc_rule();
    rule->type = GROUP;
    rule->patterns = patterns;
    hashmap_put(globalFunny->rules, token, rule);
}

void register_token(String token, String regex) {
    init_lexer();
    Rule *rule = alloc_rule();
    rule->type = TOKEN;
    rule->regex = regex;
    hashmap_put(globalFunny->rules, token, rule);
}

void cache_patterns() {
    if (pattern_cache != NULL) {
        return;
    }
    pattern_cache = hashmap_new();
    String *orders = globalFunny->lexOrders;
    while (*orders) {
        String token = *orders;
        PatternNode *n = init_pattern_node(token);
        Queue *queue = recur_pattern(n);
        hashmap_put(pattern_cache, token, queue);
        orders++;
    }
}
